package conference.controller;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Observable;
import java.util.Observer;

import javax.naming.event.ObjectChangeListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import conference.model.ClientWithThread;
import conference.model.ConferenceState.NOTIFICATION_TYPE;
import conference.model.ConferenceStateServerSide;
import conference.model.ConferenceStateServerSide.NOTIFICATION_SERVER;
import conference.TCPCommands;

/**
 * this is a thread started when a new client connects to the server.
 * The server should:
 * - store the users IP address
 * - give the client the whole list of clients when asked for.
 * @author samsjo-8
 * credit: followed example at http://pguides.net/java/tcp-client-server-chat
 * 
 */
public class ServerSideTCP implements Runnable, Observer{
    private Socket clientSocket;
    private ClientWithThread client;
    BufferedReader in = null;
    PrintWriter out = null;
    ConferenceStateServerSide conference = null;
    
  //if a 
  	String hasPrivateChatRequestFrom = null;
    

    public ServerSideTCP(Socket client, ConferenceStateServerSide conference) {
        this.clientSocket = client;
        this.conference=conference;
        conference.addObserver(this);
    }

    public void run() {
        in = null;
        out = null;
        try {
       	 System.out.println("Client connected: "+clientSocket.getInetAddress().getHostAddress());
            /* obtain an input stream to this client ... */
            in = new BufferedReader(new InputStreamReader(
                        clientSocket.getInputStream()));
            /* ... and an output stream to the same client */
            out = new PrintWriter(clientSocket.getOutputStream(), true);
        } catch (IOException e) {
            System.err.println(e);
            return;
        }

        String msg;
        
        try {
            /* loop reading messages from the client, 
             * output to stdin and send back an "OK" back */
        	
        	//initializing connection
            send(TCPCommands.server_msg_SPECIFY_NICK);
            
            
            boolean acceptedNick=false;
        	do{
        		msg = in.readLine(); // wait for new line
        		System.out.println("recieved: "+msg);
        		if(conference.nickIsInUse(msg)){//check if nick is in use
        			send(TCPCommands.server_msg_NICK_IN_USE);
        		} else {
        			send(TCPCommands.server_msg_OK);
        			System.out.println("Nick accepted.");
        			acceptedNick=true;
        		}
        	}while( msg!= null && !acceptedNick);
        	if(msg==null){
        		return;
        	}
        	//add user to list
        	client = new ClientWithThread(clientSocket.getInetAddress().getHostAddress(),msg,this);
        	conference.getList().add(client);
        	conference.setLatestUserAdded(client);
        	
        	System.out.println("Awaiting Commands");
        	//commands loop
            while ((msg = in.readLine()) != null) {
            	commandProcessing(msg);
            }
        } catch (IOException e) {
            System.err.println(e);
        }
        
        //do this when lost connection of a client, or the client has quit
        
        //remove client from list
        conference.getList().remove(client);
        conference.setLatestUserRemoved(client);
        System.out.println("removed client from list: "+client.toTransferString());
        try {
			this.finalize();
		} catch (Throwable e) {
			e.printStackTrace();
			System.out.println("failed to throw away thread");
		}
      
    }
    /** adds a message to the queue of messages to be sent. Message will be taken care of by the output thread.
    *
    *
    */
    private void send(String msg){
    	System.out.println("Sent: "+msg);
    	out.println(msg);
    }
    
    /**
     * called when a new command from the client has been received
     * @param command One of the TCPCommands
     */
    private void commandProcessing(String command){
    	System.out.println("Client command: " + command);
    	
    	if(command.equals(TCPCommands.client_msg_REQUESTING_FULL_CLIENT_LIST)){
    		send(TCPCommands.server_msg_INCOMING_FULL_CLIENT_LIST + //command
    				TCPCommands.separator_CmdMsg +					//separator
    				conference.getList().toTransferString()); 		//content
    	}

    	if(command.equals(TCPCommands.client_msg_LEAVING)){
    		conference.getList().remove(client);
    		conference.setLatestUserRemoved(client);
    	}
    	
    	if(command.equals(TCPCommands.client_msg_REQUESTING_BROADCAST_INFO)){
    		if(conference.getCurrentBroadCaster()!=null){
    			send(TCPCommands.server_msg_NEW_BROADCASTER + //command
        				TCPCommands.separator_CmdMsg +					//separator
        				conference.getCurrentBroadCaster().toTransferString()); 		//content
    		} else { //there is no broadcaster
    			send(TCPCommands.server_msg_NO_BROADCASTER);
    		}
    		
    	}
    	
    	if(command.equals(TCPCommands.client_msg_WANTS_TO_BROADCAST)){
    		conference.setBroadCaster(client);
    	}
	    	
    	//commands with more than one word
    	int index = command.indexOf(' ');
		if (index > -1) {
			String firstword = command.substring(0, index);
			String ip;
			
			if(firstword.equals(TCPCommands.SIP_msg_BYE)){
				ip = command.substring(index+1, command.indexOf(':'));  //get ip from command
				//get receiving client thread
				ServerSideTCP thread = ((ClientWithThread)(conference.getList().getClientFromIp(ip))).getThread();
				//send forward
	    		thread.send(TCPCommands.SIP_msg_BYE + //command
	    				TCPCommands.separator_CmdMsg +					//separator
	    				client.toTransferString()//content
	    		); 		
	    	}
			
			if(firstword.equals(TCPCommands.SIP_msg_OK)){
				ip = command.substring(index+1, command.indexOf(':')); //get ip from command
				//get receiving client thread
				ServerSideTCP thread = ((ClientWithThread)(conference.getList().getClientFromIp(ip))).getThread();
				//send forward
	    		thread.send(TCPCommands.SIP_msg_OK + //command
	    				TCPCommands.separator_CmdMsg +					//separator
	    				client.toTransferString()//content
	    		);
	    	}
			
			if(firstword.equals(TCPCommands.SIP_msg_INVITE)){
				System.out.println("client req. priv-chat");
				ip = command.substring(index+1, command.indexOf(':')); //get ip from command
				//get receiving client thread
				ServerSideTCP thread = ((ClientWithThread)(conference.getList().getClientFromIp(ip))).getThread();
				//send forward
	    		thread.send(TCPCommands.SIP_msg_INVITE + //command
	    				TCPCommands.separator_CmdMsg +					//separator
	    				client.toTransferString()//content
	    		);
	    	}
			
			if(command.equals(TCPCommands.SIP_msg_BUSY)){
				ip = command.substring(index+1, command.indexOf(':')); //get ip from command
				//get receiving client thread
				ServerSideTCP thread = ((ClientWithThread)(conference.getList().getClientFromIp(ip))).getThread();
				//send forward
	    		thread.send(TCPCommands.SIP_msg_BUSY + 	//command
	    				TCPCommands.separator_CmdMsg +		//separator
	    				client.toTransferString()			//content
	    		);
	    	}
			
		}
}

    /**
     * called when changes has been done to the model (the server-side conference state) by other threads
     * notifies the user of changes
     */
	@Override
	public void update(Observable arg0, Object notification) {
		System.out.println("update received: ");
		if(notification==NOTIFICATION_TYPE.BROADCASTER_UPDATE){
			send(TCPCommands.server_msg_NEW_BROADCASTER+ //command
					TCPCommands.separator_CmdMsg+		 //separator
					conference.getCurrentBroadCaster().toTransferString() //content
			);
		}
		else if(notification==NOTIFICATION_SERVER.USER_JOINED_UPDATE){
			System.out.println("update received: user joined");
			send(TCPCommands.server_msg_USER_JOINED+ //command
					TCPCommands.separator_CmdMsg+		 //separator
					conference.getLatestUserAdded().toTransferString() //content
			);
			
		}
		else if(notification==NOTIFICATION_SERVER.USER_LEFT_UPDATE){
			System.out.println("update received: user left");
			send(TCPCommands.server_msg_USER_LEFT+ //command
					TCPCommands.separator_CmdMsg+		 //separator
					conference.getLatestUserRemoved().toTransferString() //content
			);
		}
		
	}
    
}
